/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.flaviofaria.kenburnsview;

import ohos.agp.animation.Animator;
import ohos.agp.animation.timelinecurves.SpringCurve;
import ohos.agp.utils.RectFloat;

import java.security.SecureRandom;

/**
 * RandomTransitionGenerator
 *
 * @since 2021.07.12
 */
public class RandomTransitionGenerator implements TransitionGenerator {
    /**
     * Default value for the transition duration in milliseconds.
     */
    public static final int DEFAULT_TRANSITION_DURATION = 10000;

    /**
     * Minimum rect dimension factor, according to the maximum one.
     */
    private static final float MIN_RECT_FACTOR = 0.75f;

    private Animator.TimelineCurve mTransitionInterpolator;

    /**
     * The duration, in milliseconds, of each transition.
     */
    private long mTransitionDuration;
    /**
     * The last generated transition.
     */
    private Transition mLastGenTrans;
    /**
     * The bounds of the drawable when the last transition was generated.
     */
    private RectFloat mLastDrawableBounds;
    /**
     * Random object used to generate arbitrary rects.
     */
    private final SecureRandom mRandom = new SecureRandom();

    /**
     * RandomTransitionGenerator instance
     */
    public RandomTransitionGenerator() {
        this(DEFAULT_TRANSITION_DURATION, new SpringCurve());
    }

    /**
     * RandomTransitionGenerator instance
     *
     * @param transitionDuration 动画持续时间
     * @param transitionInterpolator 动画插值器
     */
    public RandomTransitionGenerator(long transitionDuration, Animator.TimelineCurve transitionInterpolator) {
        setTransitionDuration(transitionDuration);
        setTransitionInterpolator(transitionInterpolator);
    }

    @Override
    public Transition generateNextTransition(RectFloat drawableBounds, RectFloat viewport) {
        boolean firstTransition = mLastGenTrans == null;
        boolean drawableBoundsChanged = true;
        boolean viewportRatioChanged = true;

        RectFloat srcRect = null;
        RectFloat dstRect = null;

        if (!firstTransition) {
            dstRect = mLastGenTrans.getDestinyRect();
            drawableBoundsChanged = !drawableBounds.equals(mLastDrawableBounds);
            viewportRatioChanged = !MathUtils.haveSameAspectRatio(dstRect, viewport);
        }

        if (dstRect == null || drawableBoundsChanged || viewportRatioChanged) {
            srcRect = generateRandomRect(drawableBounds, viewport);
        } else {
            /**
             * Sets the destiny rect of the last transition as the source one
             * if the current drawable has the same dimensions as the one of
             * the last transition.
             */
            srcRect = dstRect;
        }
        dstRect = generateRandomRect(drawableBounds, viewport);
        mLastGenTrans = new Transition(srcRect, dstRect, mTransitionDuration,
                mTransitionInterpolator);
        mLastDrawableBounds = new RectFloat(drawableBounds);
        return mLastGenTrans;
    }

    /**
     * Generates a random rect that can be fully contained within {@code drawableBounds} and
     * has the same aspect ratio of {@code viewportRect}. The dimensions of this random rect
     * won't be higher than the largest rect with the same aspect ratio of {@code viewportRect}
     * that {@code drawableBounds} can contain. They also won't be lower than the dimensions
     * of this upper rect limit weighted by {@code MIN_RECT_FACTOR}.
     *
     * @param drawableBounds the bounds of the drawable that will be zoomed and panned.
     * @param viewportRect the bounds of the view that the drawable will be shown.
     * @return an arbitrary generated rect with the same aspect ratio of {@code viewportRect}
     * that will be contained within {@code drawableBounds}.
     */
    private RectFloat generateRandomRect(RectFloat drawableBounds, RectFloat viewportRect) {
        float drawableRatio = MathUtils.getRectRatio(drawableBounds);
        float viewportRectRatio = MathUtils.getRectRatio(viewportRect);
        RectFloat maxCrop;

        if (drawableRatio > viewportRectRatio) {
            float right = (drawableBounds.getHeight() / viewportRect.getHeight()) * viewportRect.getWidth();
            float bottom = drawableBounds.getHeight();
            maxCrop = new RectFloat(0, 0, right, bottom);
        } else {
            float right = drawableBounds.getWidth();
            float bottom = (drawableBounds.getWidth() / viewportRect.getWidth()) * viewportRect.getHeight();
            maxCrop = new RectFloat(0, 0, right, bottom);
        }

        float randomFloat = MathUtils.truncate(mRandom.nextFloat(), 2);
        float factor = MIN_RECT_FACTOR + ((1 - MIN_RECT_FACTOR) * randomFloat);

        float width = factor * maxCrop.getWidth();
        float height = factor * maxCrop.getHeight();
        int widthDiff = (int) (drawableBounds.getWidth() - width);
        int heightDiff = (int) (drawableBounds.getHeight() - height);
        int left = widthDiff > 0 ? mRandom.nextInt(widthDiff) : 0;
        int top = heightDiff > 0 ? mRandom.nextInt(heightDiff) : 0;
        return new RectFloat(left, top, left + width, top + height);
    }

    /**
     * Sets the duration, in milliseconds, for each transition generated.
     *
     * @param transitionDuration the transition duration.
     */
    public void setTransitionDuration(long transitionDuration) {
        mTransitionDuration = transitionDuration;
    }

    /**
     * setTransitionInterpolator
     *
     * @param interpolator the transition interpolator.
     */
    public void setTransitionInterpolator(Animator.TimelineCurve interpolator) {
        mTransitionInterpolator = interpolator;
    }
}
